package org.example.uitls;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * PropertiesUtils Properties 工具类。
 * 项目打包成 .jar、.war 包部署时，此时类路径文件被嵌套在 .jar、.war 包文件内部，此时资源文件内容只读，无法增删改，
 * 否则报错：java.io.FileNotFoundException: file:/x/target\x-SNAPSHOT.jar!\BOOT-INF\classes!\x.x (文件名、目录名或卷标语法不正确。)
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/4/2 16:02
 */
public class PropertiesUtils {
    /**
     * 查询类路径下的某个 .properties 文件中的所有键值数据
     *
     * @param classPath 如类路径下的 config/config.properties
     * @return
     */
    public static Map<String, String> findAllByClassPath(String classPath) {
        Map<String, String> paramMap = new Hashtable<>();
        InputStream inputStream = null;
        Logger logger = Logger.getAnonymousLogger();
        try {
            //如果 classPath 不存在，则获取的输入流会为 null
            inputStream = PropertiesUtils.class.getClassLoader().getResourceAsStream(classPath);
            if (inputStream == null) {
                // logger.warning(classPath + "不存在...");
                // return paramMap;
                throw new RuntimeException(classPath + "不存在...");
            }
            Properties properties = new Properties();
            /**
             * 从输入字节流读取属性列表（键和元素对）,此方法返回后，指定的流仍保持打开状态。
             * 输入流按 load(Reader) 中所指定的、简单的面向行的格式，并假定使用 ISO 8859-1 字符编码
             * 还有一个重载方法： load(Reader reader) 字符流
             */
            properties.load(inputStream);
            Enumeration enumeration = properties.propertyNames();
            while (enumeration.hasMoreElements()) {
                String propertiesName = (String) enumeration.nextElement();
                /**根据属性名获取属性值
                 * String getProperty(String key)：用指定的键在此属性列表中搜索属性。
                 * 如果在此属性列表中未找到该键，则接着递归检查默认属性列表及其默认值，如果还未找到属性，则此方法返回 null。
                 * 重载了一个 String getProperty(String key, String defaultValue) 方法
                 * 在原来的基础上，如果当前属性列表与默认属性列表中都没有值时，则返回 defaultValue 作为默认值
                 */
                String propertiesValue = properties.getProperty(propertiesName);
                paramMap.put(propertiesName, propertiesValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭输入流
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return paramMap;
    }

    /**
     * 向类路径下的某个属性文件添加/修改单个属性值，如果想一次添加多个，只需要在此基础上修改即可
     * 第 1 步先加载属性文件（load）、
     * 第 2 步设置属性值 （setProperty）、
     * 第 3 步保存到磁盘（store）
     * setProperty 底层因为是 Map 的 put 方法，所以如果 key 已经存在，则会进行覆盖
     *
     * @param classPath     如类路径下的 config.properties
     * @param propertyName  属性 key 值
     * @param propertyValue 属性的值
     * @throws IOException
     */
    public static void addPropertyToClassPath(String classPath, String propertyName, String propertyValue) {
        Logger logger = Logger.getAnonymousLogger();
        if (classPath == null || classPath.equals("")) {
            // logger.warning("参数错误：" + classPath);
            // return;
            throw new RuntimeException("参数错误：" + classPath);
        }
        if (propertyName == null || propertyName.equals("") || propertyValue == null || propertyValue.equals("")) {
            // logger.warning("参数错误：propertyName=" + propertyName + ",propertyValue=" + propertyValue);
            // return;
            throw new RuntimeException("参数错误：propertyName=" + propertyName + ",propertyValue=" + propertyValue);
        }
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            //如果 classPath 不存在，则获取的输入流会为 null
            inputStream = PropertiesUtils.class.getClassLoader().getResourceAsStream(classPath);
            if (inputStream == null) {
                // logger.warning("类路径错误" + classPath);
                // return;
                throw new RuntimeException("类路径错误" + classPath);
            }
            Properties properties = new Properties();
            /**
             * 从输入字节流读取属性列表（键和元素对）,此方法返回后，指定的流仍保持打开状态。
             * 输入流按 load(Reader) 中所指定的、简单的面向行的格式，并假定使用 ISO 8859-1 字符编码
             * 还有一个重载方法： load(Reader reader) 字符流
             */
            properties.load(inputStream);
            /**
             * 底层调用的是 Hashtable 的方法 put。之所以不推荐直接使用 put 方法，是因为 put 可以添加 String 以外的类型
             * 而 setProperty 是强制要求为属性的键和值使用字符串。返回值是 Hashtable 调用 put 的结果
             * 如果 key 已经存在，则会进行覆盖
             */
            properties.setProperty(propertyName, propertyValue);
            /**
             * setProperty 只是修改了内存中的值，磁盘文件并未修改
             * store(OutputStream out, String comments) ：将内容持久化到磁盘，显然底层也是 I/O 操作
             * 将此属性列表（键和元素对）写入此Properties表中，以适合于使用load(InputStream)方法加载到Properties表中的格式输出流。
             * comments - 属性列表的描述，也就是注释
             */
            URL url = PropertiesUtils.class.getClassLoader().getResource(classPath);
            outputStream = new FileOutputStream(url.getPath());
            properties.store(outputStream, "修改");
            logger.info("添加属性成功：" + url.getPath());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭流
                if (outputStream != null) {
                    outputStream.flush();
                    outputStream.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 查询类路径下的某个属性文件中指定的属性值，不存在时返回空
     *
     * @param classPath    如类路径下的 config.properties
     * @param propertyName 属性 key 值
     * @throws IOException
     */
    public static String getOneToClassPath(String classPath, String propertyName) {
        Logger logger = Logger.getAnonymousLogger();
        if (classPath == null || classPath.equals("")) {
            // logger.warning("参数错误：" + classPath);
            // return "";
            throw new RuntimeException("参数错误：" + classPath);
        }
        if (propertyName == null || propertyName.equals("")) {
            // logger.warning("参数错误：propertyName=" + propertyName);
            // return "";
            throw new RuntimeException("参数错误：propertyName=" + propertyName);
        }
        InputStream inputStream = null;
        try {
            //如果 classPath 不存在，则获取的输入流会为 null
            inputStream = PropertiesUtils.class.getClassLoader().getResourceAsStream(classPath);
            if (inputStream == null) {
                // logger.warning("类路径错误" + classPath);
                // return "";
                throw new RuntimeException("类路径错误" + classPath);
            }
            Properties properties = new Properties();
            properties.load(inputStream);
            /**
             * getProperty(String key, String defaultValue)：
             * 使用此属性列表中指定的键搜索属性。 如果在此属性列表中找不到该键，则会默认属性列表及其默认值递归。 如果找不到属性，该方法返回默认值参数 defaultValue
             */
            return properties.getProperty(propertyName, "");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭流
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    /**
     * 向类路径下的某个属性文件删除指定的属性值，如果想一次删除多个，只需要在此基础上修改即可
     * 第 1 步先加载属性文件（load）、第 2 步删除属性值 （remove）、第 3 步保存到磁盘（store）
     * remove 是 java.util.Hashtable.remove(java.lang.Object)，key 不存在时也不影响
     *
     * @param classPath    如类路径下的 config.properties
     * @param propertyName 属性 key 值
     * @param classPath
     * @param propertyName
     */
    public static void deleteOneToClassPath(String classPath, String propertyName) {
        Logger logger = Logger.getAnonymousLogger();
        if (classPath == null || classPath.equals("")) {
            logger.warning("参数错误：" + classPath);
            return;
        }
        if (propertyName == null || propertyName.equals("")) {
            logger.warning("参数错误：propertyName=" + propertyName);
            return;
        }
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            //如果 classPath 不存在，则获取的输入流会为 null
            inputStream = PropertiesUtils.class.getClassLoader().getResourceAsStream(classPath);
            if (inputStream == null) {
                logger.warning("类路径错误" + classPath);
                return;
            }
            Properties properties = new Properties();
            properties.load(inputStream);

            //这是父类的方法 java.util.Hashtable.remove(java.lang.Object)
            properties.remove(propertyName);//删除属性，key 不存在时也不影响
            /**
             * remove 只是修改了内存中的值，磁盘文件并未修改
             * store(OutputStream out, String comments) ：将内容持久化到磁盘，显然底层也是 I/O 操作
             * 将此属性列表（键和元素对）写入此Properties表中，以适合于使用load(InputStream)方法加载到Properties表中的格式输出流。
             * comments - 属性列表的描述，也就是注释
             */
            URL url = PropertiesUtils.class.getClassLoader().getResource(classPath);
            outputStream = new FileOutputStream(url.getPath());
            properties.store(outputStream, "删除");
            logger.info("删除属性成功：" + url.getPath());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭流
                if (outputStream != null) {
                    outputStream.flush();
                    outputStream.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        System.out.println("----------------测试读取所有属性配置----------------");
        String classPath = "config/config.properties";
        Map<String, String> stringMap = PropertiesUtils.findAllByClassPath(classPath);
        for (Map.Entry<String, String> entry : stringMap.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }

        System.out.println("----------------测试新增与修改属性配置----------------");
        PropertiesUtils.addPropertyToClassPath(classPath, "age", "33");
        stringMap = PropertiesUtils.findAllByClassPath(classPath);
        for (Map.Entry<String, String> entry : stringMap.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }

        System.out.println("----------------测试查询单个属性值----------------");
        System.out.println(PropertiesUtils.getOneToClassPath(classPath, "author"));

        System.out.println("----------------删除属性值----------------");
        PropertiesUtils.deleteOneToClassPath(classPath, "age");
        stringMap = PropertiesUtils.findAllByClassPath(classPath);
        for (Map.Entry<String, String> entry : stringMap.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}